﻿//RemoteStorageClient.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.History.Hydra
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;
	using System.ServiceModel;
	using System.ServiceModel.Description;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Net;

	using StockSharp.Algo.Candles;
	using StockSharp.Algo.Storages;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Клиент для доступа к серверу истории <see cref="IRemoteStorage"/>.
	/// </summary>
	public class RemoteStorageClient : Disposable
	{
		private sealed class RemoteStorage : IMarketDataStorageDrive
		{
			private readonly RemoteStorageClient _parent;
			private readonly Security _security;
			private readonly string _dataType;
			private readonly object _arg;

			public RemoteStorage(RemoteStorageClient parent, Security security, string dataType, object arg)
			{
				if (parent == null)
					throw new ArgumentNullException("parent");

				if (security == null)
					throw new ArgumentNullException("security");

				if (dataType.IsEmpty())
					throw new ArgumentNullException("dataType");

				//if (arg == null)
				//	throw new ArgumentNullException("arg");

				_parent = parent;
				_security = security;
				_dataType = dataType;
				_arg = arg;
			}

			IEnumerable<DateTime> IMarketDataStorageDrive.Dates
			{
				get { return _parent._factory.Value.Invoke(f => f.GetDates(_parent._sessionId, _security.Id, _dataType, _arg)); }
			}

			void IMarketDataStorageDrive.ClearDatesCache()
			{
				_parent._factory.Value.Invoke(f => f.ClearDatesCache(_parent._sessionId, _security.Id, _dataType, _arg));
			}

			void IMarketDataStorageDrive.Delete(DateTime date)
			{
				_parent._factory.Value.Invoke(f => f.Delete(_parent._sessionId, _security.Id, _dataType, _arg, date));
			}

			void IMarketDataStorageDrive.Save(DateTime date, byte[] rawData)
			{
				_parent._factory.Value.Invoke(f => f.Save(_parent._sessionId, _security.Id, _dataType, _arg, date, rawData));
			}

			byte[] IMarketDataStorageDrive.Load(DateTime date)
			{
				return _parent._factory.Value.Invoke(f => f.Load(_parent._sessionId, _security.Id, _dataType, _arg, date));
			}

			void IMarketDataStorageDrive.SaveStream(DateTime date, Stream rawData)
			{
				// mika
				// стриминг не поддерживает несколько входных параметров
				// http://stackoverflow.com/questions/1339857/wcf-using-streaming-with-message-contracts
				//_parent._factory.Value.Invoke(f => f.SaveStream(_parent._sessionId, _security.Id, _dataType, _arg, date, rawData));
				((IMarketDataStorageDrive)this).Save(date, rawData.To<byte[]>());
			}

			Stream IMarketDataStorageDrive.LoadStream(DateTime date)
			{
				return _parent._factory.Value.Invoke(f => f.LoadStream(_parent._sessionId, _security.Id, _dataType, _arg, date));
			}
		}

		private readonly Lazy<ChannelFactory<IRemoteStorage>> _factory;
		private Guid _sessionId;
		private readonly SynchronizedDictionary<Tuple<Security, Type, object>, RemoteStorage> _remoteStorages = new SynchronizedDictionary<Tuple<Security, Type, object>, RemoteStorage>();

		/// <summary>
		/// Создать <see cref="RemoteStorageClient"/>.
		/// </summary>
		public RemoteStorageClient()
			: this("net.tcp://localhost:8000".To<Uri>())
		{
		}

		/// <summary>
		/// Создать <see cref="RemoteStorageClient"/>.
		/// </summary>
		/// <param name="address">Адрес сервера.</param>
		/// <param name="streaming">Передача данных через WCF Streaming.</param>
		public RemoteStorageClient(Uri address, bool streaming = true)
		{
			if (address == null)
				throw new ArgumentNullException("address");

			Address = address;

			_factory = new Lazy<ChannelFactory<IRemoteStorage>>(() =>
			{
				var factory = ChannelHelper.Create("hydra", () =>
				{
					var f = new ChannelFactory<IRemoteStorage>(new NetTcpBinding(SecurityMode.None)
					{
						TransferMode = streaming ? TransferMode.StreamedResponse : TransferMode.Buffered,
						OpenTimeout = TimeSpan.FromMinutes(5),
						SendTimeout = TimeSpan.FromMinutes(40),
						ReceiveTimeout = TimeSpan.FromMinutes(40),
						MaxReceivedMessageSize = int.MaxValue,
						ReaderQuotas =
						{
							MaxArrayLength = int.MaxValue,
							MaxBytesPerRead = int.MaxValue
						},
						MaxBufferSize = int.MaxValue,
						MaxBufferPoolSize = int.MaxValue
					}, new EndpointAddress(address));

					foreach (var op in f.Endpoint.Contract.Operations)
					{
						var dataContractBehavior = op.Behaviors[typeof(DataContractSerializerOperationBehavior)] as DataContractSerializerOperationBehavior;
						
						if (dataContractBehavior != null)
							dataContractBehavior.MaxItemsInObjectGraph = int.MaxValue;
					}

					return f;
				});

				_sessionId = factory.Invoke(f => f.Login(ServerCredentials.Instance.Login, ServerCredentials.Instance.Password));

				return factory;
			});
		}

		/// <summary>
		/// Адрес сервера.
		/// </summary>
		public Uri Address { get; private set; }

		private IEnumerable<Security> GetNewSecurities(ISecurityStorage securityStorage, out IEnumerable<string> ids)
		{
			ids = _factory.Value.Invoke(f => f.GetAllSecurityIds(_sessionId));

			var newSecurityIds = ids
					.Where(id => securityStorage.LoadBy("Id", id) == null)
					.ToArray();

			var newSecurities = _factory.Value.Invoke(f => f.GetSecurities(_sessionId, newSecurityIds));
			newSecurities.ForEach(securityStorage.Save);
			return newSecurities;
		}

		/// <summary>
		/// Получить все инструменты.
		/// </summary>
		/// <param name="securityStorage">Интерфейс для доступа к хранилищу информации об инструментах.</param>
		/// <returns>Все инструменты.</returns>
		public IEnumerable<Security> GetAllSecurities(ISecurityStorage securityStorage)
		{
			IEnumerable<string> ids;
			GetNewSecurities(securityStorage, out ids);

			return ids
					.Select(id => securityStorage.LoadBy("Id", id))
					.Where(s => s != null)
					.ToArray();
		}

		/// <summary>
		/// Получить новые инструменты.
		/// </summary>
		/// <param name="securityStorage">Интерфейс для доступа к хранилищу информации об инструментах.</param>
		/// <returns>Новые инструменты.</returns>
		public IEnumerable<Security> GetNewSecurities(ISecurityStorage securityStorage)
		{
			IEnumerable<string> ids;
			return GetNewSecurities(securityStorage, out ids);
		}

		/// <summary>
		/// Получить обертку для доступа к удаленным маркет-данным.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="dataType">Тип маркет-данных.</param>
		/// <param name="arg">Параметр, ассоциированный с типом <paramref name="dataType"/>. Например, <see cref="Candle.Arg"/>.</param>
		/// <returns>Обертка для доступа к удаленным маркет-данным.</returns>
		public IMarketDataStorageDrive GetRemoteStorage(Security security, Type dataType, object arg)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (dataType == null)
				throw new ArgumentNullException("dataType");

			return _remoteStorages.SafeAdd(new Tuple<Security, Type, object>(security, dataType, arg),
				key => new RemoteStorage(this, security, dataType.AssemblyQualifiedName, arg));
		}

		/// <summary>
		/// Получить для инструмента доступные типы свечек с параметрами.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Доступные типы свечек с параметрами.</returns>
		public IEnumerable<Tuple<Type, object[]>> GetCandleTypes(Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			return _factory.Value.Invoke(f => f.GetCandleTypes(_sessionId, security.Id));
		}

		/// <summary>
		/// Освободить занятые ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			if (_factory.IsValueCreated)
			{
				_factory.Value.Invoke(f => f.Logout(_sessionId));
				((IDisposable)_factory.Value).Dispose();
			}

			base.DisposeManaged();
		}
	}
}